"""
单核CPU实现多任务原理：操作系统轮流让各个任务交替执行，由于CPU处理速度快，所以看起来和同时进行一样
多核CPU实现多任务原理：真正的秉性执行多任务只能在多核CPU上实现，但是由于任务数量远远多于CPU的核心数量，所以操作系统也会自动把很多任务轮流调度到每个核心上执行

并发：
是指多个任务在同一时间段内交替执行，但不一定同时运行。操作系统通过快速切换任务来实现并发，使得多个任务看起来像是同时运行的
并行：
是指多个任务同时运行，通常是通过多核处理器或分布式系统实现的。并行任务可以同时执行，因为它们可以分配到不同的CPU核心或不同的机器上

实现多任务的方式：
多进程模式：
多线程模式：
协程

进程 > 线程 > 协程

from multiprocessing import Process

process = Process(target=函数，name=进程的名字，args=(给函数传递的参数))
process 对象

对象调用方法：
process.start()   启动进程并执行任务
process.run()   只是执行了任务但是没有启动进程
terminate()    终止

多进程对于全局变量访问，在每一个全局变量里面都放一个m变量
保证每一个进程变量互不干扰
m = 1     # 不可变类型
list = []     # 可变类型
"""
import multiprocessing
# 进程创建
from multiprocessing import Process
import time
import os

m = 1


# 两个子进程对全局变量的调用
def task1(s, name):
    global m
    while True:
        time.sleep(s)
        # os.getpid()表示获得当前进程号，os.getppid()表示获得父类进程号
        print("这是任务1.....", os.getpid(), "-----", os.getppid(), name, m)


def task2(s, name):
    global m
    while True:
        time.sleep(s)
        print("这是任务2.....", os.getpid(), "-----", os.getppid(), name, m)


number = 1
if __name__ == "__main__":
    print(os.getpid())
    # 子进程
    p1 = Process(target=task1, name="任务1", args=(1, "aa"))
    p1.start()
    print(p1.name)
    p2 = Process(target=task2, name="任务2", args=(2, "bb"))
    p2.start()
    print(p2.name)

    while True:
        number += 1
        time.sleep(0.1)
        if number == 30:
            p1.terminate()
            p2.terminate()
            break
        else:
            print("-----number:", number)

    # 主进程对全局变量的调用
    while True:
        time.sleep(0.5)
        m += 1
        print("----->main:", m)
        if m == 10:
            break

    print('--------------')
    print("**************")


# 进程：自定义
class MyProcess(Process):

    def __init__(self, name):
        super().__init__()
        self.name = name

    # 重写run方法
    def run(self):
        n = 1
        while True:
            # print("进程名字:" + self.name)
            print(f"{n}---------->自定义进程，n:{self.name}")
            n += 1
            if n > 10:  # 添加一个退出条件，避免无限循环
                break


if __name__ == "__main__":
    p3 = MyProcess("小明")
    p3.start()
    p4 = MyProcess("小红")
    p4.start()

"""
当需要创建的子进程数量不多时，可以直接利用multiprocessing中的Process动态生成多个进程，
但如果是上百个甚至上千个目标，手动的去创建进程的工作量巨大，
此时就可以用到multiprocessing模块提供的Pool方法
初始化Pool时，可以指定一个最大进程数，
当有新的请求提交到Pool中时，如果池还没有满，那么就会创建一个新的进程用来执行该请求
但如果池中的进程数已经达到指定的最大值，
那么该请求就会等待，直到池中有进程结束，才会创建新的进程来执行

非阻塞式：全部添加到队列中，立刻返回（返回值），并没有等待其他的进程完毕，但是回调函数是等待任务完成之后才调用
阻塞式：添加一个执行一个任务，如果一个任务不结束，另一个任务就进不来

进程池：
pool = Pool(max)    创建进程池对象
pool.apply()     阻塞的
pool.apply_async()     非阻塞的
pool.close()
pool.join()      让主进程让步
"""
from multiprocessing import Pool
from random import random


# 非阻塞式进程
def task(task_name):
    print("开始做任务啦！", task_name)
    start = time.time()
    # 使用sleep
    time.sleep(random() * 2)
    end = time.time()
    # print(f"完成任务{task_name}!用时：{(end - start)},进程ID：{os.getpid()}")
    return f"完成任务{task_name}!用时：{(end - start)},进程ID：{os.getpid()}"


container = []


# 定义一个回调函数，里面必须接参数
def callback_func(n):
    container.append(n)


if __name__ == '__main__':
    pool = Pool(5)
    tasks = ["敲代码", "看电视", "打射雕", "刷抖音", "吃饭", "睡觉", "学网课"]
    for task1 in tasks:
        # apply_async函数表示采用Pool中的非阻塞进程模式，异步启动任务，并指定回调函数callback_func
        # 由于任务是异步执行的，任务完成的顺序可能与任务启动的顺序不同
        # callback往往是进程结束了之后才会调用的函数
        pool.apply_async(task, args=(task1,), callback=callback_func)

    pool.close()    # 关闭进程池，不再接受新的任务
    pool.join()     # 用于等待进程池中的所有进程完成,确保主程序不会在子进程完成之前退出

    for c in container:
        print(c)
    print("over!!!!!")


# 阻塞式进程
def task(task_name):
    print("开始做任务啦！", task_name)
    start = time.time()
    # 使用sleep
    time.sleep(random() * 2)
    end = time.time()
    print(f"完成任务{task_name}!用时：{(end - start)},进程ID：{os.getpid()}")
    # return f"完成任务{task_name}!用时：{(end - start)},进程ID：{os.getpid()}"


if __name__ == '__main__':
    pool = Pool(5)
    tasks = ["敲代码", "看电视", "打射雕", "刷抖音", "吃饭", "睡觉", "学网课"]
    for task1 in tasks:
        pool.apply(task, args=(task1,))

    pool.close()    # 关闭进程池，不再接受新的任务
    pool.join()     # 用于等待进程池中的所有进程完成,确保主程序不会在子进程完成之前退出
    print("over!!!!!")


# 进程间通信
from multiprocessing import Queue

q = Queue(5)
q.put("1")
q.put("2")
q.put("3")
q.put("4")
q.put("5")
print(q.qsize())      # 得到队列的长度
if not q.full():   # 判断队列是否满了    q.empty()表示判断队列是否是空的
    q.put("6", timeout=3)     # 如果queue满了则只能等待，除非有"空地"则添加成功
else:
    print("队列已满！")
# 获取队列的值
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get(timeout=2))     # 等待两秒后没有获取成功则返回异常


# 进程间通信
def download(q):
    images = ["boy.jpg", "girl.jpg", "man.jpg"]
    for image in images:
        print("正在下载：", image)
        time.sleep(0.5)
        q.put(image)


def getfile(q):
    while True:
        try:
            file = q.get()
            print(f"{file}保存成功！")
        except:
            print("全部保存完毕！")
            break


if __name__ == '__main__':
    q = Queue(5)
    p1 = Process(target=download, args=(q,))
    p2 = Process(target=getfile, args=(q,))
    p1.start()
    p1.join()      # 相当于让p1插队了，优先让p1先做
    p2.start()
    p2.join()     # 阻塞一下主进程，防止子进程还未运行完就将参数回收了
    print("000000000")
